
package com.android.vending.expansion.zipfile;

import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//To implement APEZProvider in your application, you'll want to change
//the AUTHORITY to match what you define in the manifest.

import com.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

/**
 * This content provider is an optional part of the library.
 * <p>
 * Most apps don't need to use this class. This defines a ContentProvider that marshalls the data
 * from the ZIP files through a content provider Uri in order to provide file access for certain
 * Android APIs that expect Uri access to media files.
 */
public abstract class APEZProvider extends ContentProvider {

    private ZipResourceFile mAPKExtensionFile;
    private boolean mInit;

    public static final String FILEID = BaseColumns._ID;
    public static final String FILENAME = "ZPFN";
    public static final String ZIPFILE = "ZFIL";
    public static final String MODIFICATION = "ZMOD";
    public static final String CRC32 = "ZCRC";
    public static final String COMPRESSEDLEN = "ZCOL";
    public static final String UNCOMPRESSEDLEN = "ZUNL";
    public static final String COMPRESSIONTYPE = "ZTYP";

    public static final String[] ALL_FIELDS = {
            FILEID,
            FILENAME,
            ZIPFILE,
            MODIFICATION,
            CRC32,
            COMPRESSEDLEN,
            UNCOMPRESSEDLEN,
            COMPRESSIONTYPE
    };

    public static final int FILEID_IDX = 0;
    public static final int FILENAME_IDX = 1;
    public static final int ZIPFILE_IDX = 2;
    public static final int MOD_IDX = 3;
    public static final int CRC_IDX = 4;
    public static final int COMPLEN_IDX = 5;
    public static final int UNCOMPLEN_IDX = 6;
    public static final int COMPTYPE_IDX = 7;

    public static final int[] ALL_FIELDS_INT = {
            FILEID_IDX,
            FILENAME_IDX,
            ZIPFILE_IDX,
            MOD_IDX,
            CRC_IDX,
            COMPLEN_IDX,
            UNCOMPLEN_IDX,
            COMPTYPE_IDX
    };

    /**
     * This needs to match the authority in your manifest
     */
    public abstract String getAuthority();

    @Override
    public int delete(Uri arg0, String arg1, String[] arg2) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return "vnd.android.cursor.item/asset";
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub
        return null;
    }

    static private final String NO_FILE = "N";

    private boolean initIfNecessary() {
        if (!mInit) {
            Context ctx = getContext();
            PackageManager pm = ctx.getPackageManager();
            ProviderInfo pi = pm.resolveContentProvider(getAuthority(),
                    PackageManager.GET_META_DATA);
            PackageInfo packInfo;
            try {
                packInfo = pm.getPackageInfo(ctx.getPackageName(), 0);
            } catch (NameNotFoundException e1) {
                e1.printStackTrace();
                return false;
            }
            int patchFileVersion;
            int mainFileVersion;
            int appVersionCode = packInfo.versionCode;
            String[] resourceFiles = null;
            if (null != pi.metaData) {
                mainFileVersion = pi.metaData.getInt("mainVersion", appVersionCode);
                patchFileVersion = pi.metaData.getInt("patchVersion", appVersionCode);
                String mainFileName = pi.metaData.getString("mainFilename");
                if (null != mainFileName) {
                    String patchFileName = pi.metaData.getString("patchFilename");
                    if (null != patchFileName) {
                        resourceFiles = new String[] {
                                mainFileName, patchFileName
                        };
                    } else {
                        resourceFiles = new String[] {
                                mainFileName
                        };
                    }
                }
            } else {
                mainFileVersion = patchFileVersion = appVersionCode;
            }
            try {
                if (null == resourceFiles) {
                    mAPKExtensionFile = APKExpansionSupport.getAPKExpansionZipFile(ctx,
                            mainFileVersion, patchFileVersion);
                } else {
                    mAPKExtensionFile = APKExpansionSupport.getResourceZipFile(resourceFiles);
                }
                mInit = true;
                return true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public AssetFileDescriptor openAssetFile(Uri uri, String mode)
            throws FileNotFoundException {
        initIfNecessary();
        String path = uri.getEncodedPath();
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        return mAPKExtensionFile.getAssetFileDescriptor(path);
    }

    @Override
    public ContentProviderResult[] applyBatch(
            ArrayList<ContentProviderOperation> operations)
                    throws OperationApplicationException {
        initIfNecessary();
        return super.applyBatch(operations);
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode)
            throws FileNotFoundException {
        initIfNecessary();
        AssetFileDescriptor af = openAssetFile(uri, mode);
        if (null != af) {
            return af.getParcelFileDescriptor();
        }
        return null;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        initIfNecessary();
        // lists all of the items in the file that match
        ZipEntryRO[] zipEntries;
        if (null == mAPKExtensionFile) {
            zipEntries = new ZipEntryRO[0];
        } else {
            zipEntries = mAPKExtensionFile.getAllEntries();
        }
        int[] intProjection;
        if (null == projection) {
            intProjection = ALL_FIELDS_INT;
            projection = ALL_FIELDS;
        } else {
            int len = projection.length;
            intProjection = new int[len];
            for (int i = 0; i < len; i++) {
                if (projection[i].equals(FILEID)) {
                    intProjection[i] = FILEID_IDX;
                } else if (projection[i].equals(FILENAME)) {
                    intProjection[i] = FILENAME_IDX;
                } else if (projection[i].equals(ZIPFILE)) {
                    intProjection[i] = ZIPFILE_IDX;
                } else if (projection[i].equals(MODIFICATION)) {
                    intProjection[i] = MOD_IDX;
                } else if (projection[i].equals(CRC32)) {
                    intProjection[i] = CRC_IDX;
                } else if (projection[i].equals(COMPRESSEDLEN)) {
                    intProjection[i] = COMPLEN_IDX;
                } else if (projection[i].equals(UNCOMPRESSEDLEN)) {
                    intProjection[i] = UNCOMPLEN_IDX;
                } else if (projection[i].equals(COMPRESSIONTYPE)) {
                    intProjection[i] = COMPTYPE_IDX;
                } else {
                    throw new RuntimeException();
                }
            }
        }
        MatrixCursor mc = new MatrixCursor(projection, zipEntries.length);
        int len = intProjection.length;
        for (ZipEntryRO zer : zipEntries) {
            MatrixCursor.RowBuilder rb = mc.newRow();
            for (int i = 0; i < len; i++) {
                switch (intProjection[i]) {
                    case FILEID_IDX:
                        rb.add(i);
                        break;
                    case FILENAME_IDX:
                        rb.add(zer.mFileName);
                        break;
                    case ZIPFILE_IDX:
                        rb.add(zer.getZipFileName());
                        break;
                    case MOD_IDX:
                        rb.add(zer.mWhenModified);
                        break;
                    case CRC_IDX:
                        rb.add(zer.mCRC32);
                        break;
                    case COMPLEN_IDX:
                        rb.add(zer.mCompressedLength);
                        break;
                    case UNCOMPLEN_IDX:
                        rb.add(zer.mUncompressedLength);
                        break;
                    case COMPTYPE_IDX:
                        rb.add(zer.mMethod);
                        break;
                }
            }
        }
        return mc;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        return 0;
    }

}
